Fix duplicate SEO issues with Apache, NGINX, and FrankenPHP #646
+24
−0
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Background
A discussion on r/laravel identified that applications running on serversideup/php images (including those on Laravel Cloud) serve identical content at both
/pathand/index.php/path. The original poster discovered this because Bing had begun indexing/index.php/...URLs alongside the canonical clean URLs.They were specifically referencing this line as an issue:
docker-php/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template
Lines 35 to 37 in 6c8c8ca
Where this configuration came from
This configuration has been used internally with our team for over a decade and continues to be in every example that we can find in a Google search for "Laravel FPM NGINX configuration". Few examples:
Further investigation
After running some tests, we found this issue is across all our web server variations:
Problem
All three web server variations (
fpm-nginx,fpm-apache,frankenphp) allow direct browser and crawler requests to/index.php/some/pathto reach PHP, where the framework strips the/index.phpprefix and routes the request normally. This means every route in an application has two publicly accessible URLs that return identical content:This causes three concrete SEO problems:
Proposed solution
Add a
301 Moved Permanentlyredirect that intercepts direct requests to/index.php/...and redirects to the clean URL, preserving query strings. This is applied across all three web server variations.nginx (`fpm-nginx`)
Added to both
http.conf.templateandhttps.conf.template:The regex
^/index\.php(/.+)$requires at least one character after/index.php/, so the internaltry_filesrewrite to bare/index.phpis never matched. Normal Laravel routing is completely unaffected.Apache (`fpm-apache`)
Added to both
http.confandhttps.conf:The
RewriteCondmatches against%{THE_REQUEST}(the original HTTP request line from the client), which does not change during internal rewrites. This ensures only direct client requests to/index.php/...trigger the redirect, not internal.htaccessprocessing.Note: The Apache variation relies on the application's
.htaccessfile to route clean URLs toindex.php(viaAllowOverride All). Both Laravel and WordPress ship.htaccessfiles that handle this by default.Caddy/FrankenPHP (`frankenphp`)
Added to the `(php-app-common)` snippet in the Caddyfile:
Caddy evaluates
redirbeforephp_serverin its directive ordering. Caddy also preserves the original query string automatically when the redirect URI does not contain one.Why 301 redirect (not 404)
/index.php/...URLs are transferred to the clean URL. Bookmarks and external links continue to work./index.php/...URLs.How this affects sites that care about SEO
/index.php/...URLs will follow the 301 and update their index to the clean URLrel=canonicaltag orrobots.txtrules are needed — the server handles it at the HTTP level, which is the most authoritative signalHow to test
You can test this image using our serversideup/php-dev repository, which automatically builds on push to this PR.
View the available testing images →
Further comment
If you support this change, please vote with a 👍 on this PR. If you disagree or have an alternative approach, please vote with a 👎 and comment below with your proposed solution and reasoning.